//
//  SAA5050.cpp
//  Clock Signal
//
//  Created by Thomas Harte on 24/09/2025.
//  Copyright © 2025 Thomas Harte. All rights reserved.
//

#include "SAA5050.hpp"

#include <algorithm>
#include <cstdint>

namespace {
// SAA5050 font, padded out to one byte per row. The least-significant five bits of each byte
// are the meaningful pixels for that row, with the LSB being on the right.
constexpr uint8_t font[][10] = {
	{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, },	// Character 32.
	{0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x04, 0x00, 0x00, },
	{0x00, 0x0a, 0x0a, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, },
	{0x00, 0x06, 0x09, 0x08, 0x1c, 0x08, 0x08, 0x1f, 0x00, 0x00, },
	{0x00, 0x0e, 0x15, 0x14, 0x0e, 0x05, 0x15, 0x0e, 0x00, 0x00, },
	{0x00, 0x18, 0x19, 0x02, 0x04, 0x08, 0x13, 0x03, 0x00, 0x00, },
	{0x00, 0x08, 0x14, 0x14, 0x08, 0x15, 0x12, 0x0d, 0x00, 0x00, },
	{0x00, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, },
	{0x00, 0x02, 0x04, 0x08, 0x08, 0x08, 0x04, 0x02, 0x00, 0x00, },
	{0x00, 0x08, 0x04, 0x02, 0x02, 0x02, 0x04, 0x08, 0x00, 0x00, },
	{0x00, 0x04, 0x15, 0x0e, 0x04, 0x0e, 0x15, 0x04, 0x00, 0x00, },
	{0x00, 0x00, 0x04, 0x04, 0x1f, 0x04, 0x04, 0x00, 0x00, 0x00, },
	{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x08, 0x00, },
	{0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, },
	{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, },
	{0x00, 0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x00, 0x00, 0x00, },
	{0x00, 0x04, 0x0a, 0x11, 0x11, 0x11, 0x0a, 0x04, 0x00, 0x00, },
	{0x00, 0x04, 0x0c, 0x04, 0x04, 0x04, 0x04, 0x0e, 0x00, 0x00, },
	{0x00, 0x0e, 0x11, 0x01, 0x06, 0x08, 0x10, 0x1f, 0x00, 0x00, },
	{0x00, 0x1f, 0x01, 0x02, 0x06, 0x01, 0x11, 0x0e, 0x00, 0x00, },
	{0x00, 0x02, 0x06, 0x0a, 0x12, 0x1f, 0x02, 0x02, 0x00, 0x00, },
	{0x00, 0x1f, 0x10, 0x1e, 0x01, 0x01, 0x11, 0x0e, 0x00, 0x00, },
	{0x00, 0x06, 0x08, 0x10, 0x1e, 0x11, 0x11, 0x0e, 0x00, 0x00, },
	{0x00, 0x1f, 0x01, 0x02, 0x04, 0x08, 0x08, 0x08, 0x00, 0x00, },
	{0x00, 0x0e, 0x11, 0x11, 0x0e, 0x11, 0x11, 0x0e, 0x00, 0x00, },
	{0x00, 0x0e, 0x11, 0x11, 0x0f, 0x01, 0x02, 0x0c, 0x00, 0x00, },
	{0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, },
	{0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x04, 0x08, 0x00, },
	{0x00, 0x02, 0x04, 0x08, 0x10, 0x08, 0x04, 0x02, 0x00, 0x00, },
	{0x00, 0x00, 0x00, 0x1f, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, },
	{0x00, 0x08, 0x04, 0x02, 0x01, 0x02, 0x04, 0x08, 0x00, 0x00, },
	{0x00, 0x0e, 0x11, 0x02, 0x04, 0x04, 0x00, 0x04, 0x00, 0x00, },
	{0x00, 0x0e, 0x11, 0x17, 0x15, 0x17, 0x10, 0x0e, 0x00, 0x00, },
	{0x00, 0x04, 0x0a, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x00, 0x00, },
	{0x00, 0x1e, 0x11, 0x11, 0x1e, 0x11, 0x11, 0x1e, 0x00, 0x00, },
	{0x00, 0x0e, 0x11, 0x10, 0x10, 0x10, 0x11, 0x0e, 0x00, 0x00, },
	{0x00, 0x1e, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, },
	{0x00, 0x1f, 0x10, 0x10, 0x1e, 0x10, 0x10, 0x1f, 0x00, 0x00, },
	{0x00, 0x1f, 0x10, 0x10, 0x1e, 0x10, 0x10, 0x10, 0x00, 0x00, },
	{0x00, 0x0e, 0x11, 0x10, 0x10, 0x13, 0x11, 0x0f, 0x00, 0x00, },
	{0x00, 0x11, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x11, 0x00, 0x00, },
	{0x00, 0x0e, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0e, 0x00, 0x00, },
	{0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x11, 0x0e, 0x00, 0x00, },
	{0x00, 0x11, 0x12, 0x14, 0x18, 0x14, 0x12, 0x11, 0x00, 0x00, },
	{0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1f, 0x00, 0x00, },
	{0x00, 0x11, 0x1b, 0x15, 0x15, 0x11, 0x11, 0x11, 0x00, 0x00, },
	{0x00, 0x11, 0x11, 0x19, 0x15, 0x13, 0x11, 0x11, 0x00, 0x00, },
	{0x00, 0x0e, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, },
	{0x00, 0x1e, 0x11, 0x11, 0x1e, 0x10, 0x10, 0x10, 0x00, 0x00, },
	{0x00, 0x0e, 0x11, 0x11, 0x11, 0x15, 0x12, 0x0d, 0x00, 0x00, },
	{0x00, 0x1e, 0x11, 0x11, 0x1e, 0x14, 0x12, 0x11, 0x00, 0x00, },
	{0x00, 0x0e, 0x11, 0x10, 0x0e, 0x01, 0x11, 0x0e, 0x00, 0x00, },
	{0x00, 0x1f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, },
	{0x00, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, },
	{0x00, 0x11, 0x11, 0x11, 0x0a, 0x0a, 0x04, 0x04, 0x00, 0x00, },
	{0x00, 0x11, 0x11, 0x11, 0x15, 0x15, 0x15, 0x0a, 0x00, 0x00, },
	{0x00, 0x11, 0x11, 0x0a, 0x04, 0x0a, 0x11, 0x11, 0x00, 0x00, },
	{0x00, 0x11, 0x11, 0x0a, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, },
	{0x00, 0x1f, 0x01, 0x02, 0x04, 0x08, 0x10, 0x1f, 0x00, 0x00, },
	{0x00, 0x00, 0x04, 0x08, 0x1f, 0x08, 0x04, 0x00, 0x00, 0x00, },
	{0x00, 0x10, 0x10, 0x10, 0x10, 0x16, 0x01, 0x02, 0x04, 0x07, },
	{0x00, 0x00, 0x04, 0x02, 0x1f, 0x02, 0x04, 0x00, 0x00, 0x00, },
	{0x00, 0x00, 0x04, 0x0e, 0x15, 0x04, 0x04, 0x00, 0x00, 0x00, },
	{0x00, 0x0a, 0x0a, 0x1f, 0x0a, 0x1f, 0x0a, 0x0a, 0x00, 0x00, },
	{0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, },
	{0x00, 0x00, 0x00, 0x0e, 0x01, 0x0f, 0x11, 0x0f, 0x00, 0x00, },
	{0x00, 0x10, 0x10, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, },
	{0x00, 0x00, 0x00, 0x0f, 0x10, 0x10, 0x10, 0x0f, 0x00, 0x00, },
	{0x00, 0x01, 0x01, 0x0f, 0x11, 0x11, 0x11, 0x0f, 0x00, 0x00, },
	{0x00, 0x00, 0x00, 0x0e, 0x11, 0x1f, 0x10, 0x0e, 0x00, 0x00, },
	{0x00, 0x02, 0x04, 0x04, 0x0e, 0x04, 0x04, 0x04, 0x00, 0x00, },
	{0x00, 0x00, 0x00, 0x0f, 0x11, 0x11, 0x11, 0x0f, 0x01, 0x0e, },
	{0x00, 0x10, 0x10, 0x1e, 0x11, 0x11, 0x11, 0x11, 0x00, 0x00, },
	{0x00, 0x04, 0x00, 0x0c, 0x04, 0x04, 0x04, 0x0e, 0x00, 0x00, },
	{0x00, 0x04, 0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, },
	{0x00, 0x08, 0x08, 0x09, 0x0a, 0x0c, 0x0a, 0x09, 0x00, 0x00, },
	{0x00, 0x0c, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0e, 0x00, 0x00, },
	{0x00, 0x00, 0x00, 0x1a, 0x15, 0x15, 0x15, 0x15, 0x00, 0x00, },
	{0x00, 0x00, 0x00, 0x1e, 0x11, 0x11, 0x11, 0x11, 0x00, 0x00, },
	{0x00, 0x00, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, },
	{0x00, 0x00, 0x00, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x10, 0x10, },
	{0x00, 0x00, 0x00, 0x0f, 0x11, 0x11, 0x11, 0x0f, 0x01, 0x01, },
	{0x00, 0x00, 0x00, 0x0b, 0x0c, 0x08, 0x08, 0x08, 0x00, 0x00, },
	{0x00, 0x00, 0x00, 0x0f, 0x10, 0x0e, 0x01, 0x1e, 0x00, 0x00, },
	{0x00, 0x04, 0x04, 0x0e, 0x04, 0x04, 0x04, 0x02, 0x00, 0x00, },
	{0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x0f, 0x00, 0x00, },
	{0x00, 0x00, 0x00, 0x11, 0x11, 0x0a, 0x0a, 0x04, 0x00, 0x00, },
	{0x00, 0x00, 0x00, 0x11, 0x11, 0x15, 0x15, 0x0a, 0x00, 0x00, },
	{0x00, 0x00, 0x00, 0x11, 0x0a, 0x04, 0x0a, 0x11, 0x00, 0x00, },
	{0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x0f, 0x01, 0x0e, },
	{0x00, 0x00, 0x00, 0x1f, 0x02, 0x04, 0x08, 0x1f, 0x00, 0x00, },
	{0x00, 0x10, 0x10, 0x10, 0x10, 0x11, 0x03, 0x05, 0x07, 0x01, },
	{0x00, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x00, 0x00, },
	{0x00, 0x18, 0x04, 0x18, 0x04, 0x19, 0x03, 0x05, 0x07, 0x01, },
	{0x00, 0x00, 0x04, 0x00, 0x1f, 0x00, 0x04, 0x00, 0x00, 0x00, },
	{0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x00, 0x00, },
};

enum ControlCode: uint8_t {
	RedAlpha = 0x01,
	GreenAlpha = 0x02,
	YellowAlpha = 0x03,
	BlueAlpha = 0x04,
	MagentaAlpha = 0x05,
	CyanAlpha = 0x06,
	WhiteAlpha = 0x07,

	Flash = 0x08,
	Steady = 0x09,

	RedGraphics = 0x11,
	GreenGraphics = 0x12,
	YellowGraphics = 0x13,
	BlueGraphics = 0x14,
	MagentaGraphics = 0x15,
	CyanGraphics = 0x16,
	WhiteGraphics = 0x17,

	Conceal = 0x18,

	ContinuousGraphics = 0x19,
	SeparatedGraphics = 0x1a,

	NormalHeight = 0xc,
	DoubleHeight = 0xd,

	BlackBackground = 0x1c,
	NewBackground = 0x1d,

	HoldGraphics = 0x1e,
	ReleaseGraphics = 0x1f,
};}

using namespace Mullard;

void SAA5050Serialiser::begin_frame(const bool is_odd) {
	line_ = -2;
	row_ = 0;
	odd_frame_ = is_odd;

	row_has_double_height_ = false;
	double_height_offset_ = 0;

	++frame_counter_;
}

void SAA5050Serialiser::begin_line() {
	line_ += 2;
	if(line_ == 20) {
		line_ = 0;
		++row_;

		if(row_has_double_height_) {
			double_height_offset_ = (double_height_offset_ + 5) % 10;
		}
		row_has_double_height_ = false;
	}

	output_.reset();
	has_output_ = false;

	apply_control(ControlCode::WhiteAlpha);
	apply_control(ControlCode::Steady);
	apply_control(ControlCode::NormalHeight);
	apply_control(ControlCode::ContinuousGraphics);
	apply_control(ControlCode::BlackBackground);
	apply_control(ControlCode::ReleaseGraphics);
}

bool SAA5050Serialiser::has_output() const {
	return has_output_;
}

SAA5050Serialiser::Output SAA5050Serialiser::output() {
	has_output_ = false;
	return output_;
}

void SAA5050Serialiser::apply_control(const uint8_t value) {
	const auto set_alpha = [&](const uint8_t colour) {
		alpha_mode_ = true;
		conceal_ = false;
		output_.alpha = colour;
		hold_graphics_ = false;
	};

	const auto set_graphics = [&](const uint8_t colour) {
		alpha_mode_ = false;
		conceal_ = false;
		output_.alpha = colour;
		hold_graphics_ = false;
	};

	switch(value) {
		default: break;

		case RedAlpha:				set_alpha(0b100);									break;
		case GreenAlpha:			set_alpha(0b010);									break;
		case YellowAlpha:			set_alpha(0b110);									break;
		case BlueAlpha:				set_alpha(0b001);									break;
		case MagentaAlpha:			set_alpha(0b101);									break;
		case CyanAlpha:				set_alpha(0b011);									break;
		case WhiteAlpha:			set_alpha(0b111);									break;

		case Flash:					flash_ = true;										break;
		case Steady:				flash_ = false;										break;

		case RedGraphics:			set_graphics(0b100);								break;
		case GreenGraphics:			set_graphics(0b010);								break;
		case YellowGraphics:		set_graphics(0b110);								break;
		case BlueGraphics:			set_graphics(0b001);								break;
		case MagentaGraphics:		set_graphics(0b101);								break;
		case CyanGraphics:			set_graphics(0b011);								break;
		case WhiteGraphics:			set_graphics(0b111);								break;

		case Conceal:				conceal_ = true;									break;

		case ContinuousGraphics:	separated_graphics_ = false;						break;
		case SeparatedGraphics:		separated_graphics_ = true;							break;

		case NormalHeight:			double_height_ = false;								break;
		case DoubleHeight:			double_height_ = row_has_double_height_ = true;		break;

		case BlackBackground:		output_.background = 0;								break;
		case NewBackground:			output_.background = output_.alpha;					break;

		case HoldGraphics:			hold_graphics_ = true;								break;
		case ReleaseGraphics:		hold_graphics_ = false; last_graphic_ = 32;			break;
	}
}

void SAA5050Serialiser::set_reveal(const bool reveal) {
	reveal_ = reveal;
}

void SAA5050Serialiser::add(const Numeric::SizedInt<7> c) {
	has_output_ = true;
	if(c.get() < 32) {
		if(hold_graphics_) {
			load_pixels(last_graphic_);
		} else {
			output_.reset();
		}
		apply_control(c.get());
		return;
	}
	load_pixels(c.get());
}

void SAA5050Serialiser::load_pixels(const uint8_t c) {
	if(flash_ && ((frame_counter_&31) > 23)) {	// Complete guess on the blink period here.
		output_.reset();
		return;
	}

	if(conceal_ && !reveal_) {
		output_.reset();
		return;
	}

	// Divert into graphics only if both the mode and the character code allows it.
	if(!alpha_mode_ && (c & (1 << 5))) {
		last_graphic_ = c;

		// Graphics layout:
		//
		//	|----|----|
		//	|    |    |
		//	| b0 | b1 |
		//	|    |    |
		//	|----|----|
		//	|    |    |
		//	| b2 | b3 |
		//	|    |    |
		//	|----|----|
		//	|    |    |
		//	| b4 | b6 |
		//	|    |    |
		//	|----|----|

		if(separated_graphics_ && (line_ == 6 || line_ == 12 || line_ == 18)) {
			output_.reset();
			return;
		}

		uint8_t pixels;
		if(line_ < 6) {
			pixels =
				((c & 1) ? 0b111'000 : 0) |
				((c & 2) ? 0b000'111 : 0);
		} else if(line_ < 14) {
			pixels =
				((c & 4) ? 0b111'000 : 0) |
				((c & 8) ? 0b000'111 : 0);
		} else {
			pixels =
				((c & 16) ? 0b111'000 : 0) |
				((c & 64) ? 0b000'111 : 0);
		}

		if(separated_graphics_) {
			pixels &= 0b011'011;
		}

		output_.load(pixels);
		return;
	}

	if(double_height_) {
		const auto top_address = (line_ >> 2) + double_height_offset_;
		const uint8_t top = font[c - 32][top_address];
		const uint8_t bottom = font[c - 32][std::min(9, top_address + 1)];

		if(line_ & 2) {
			output_.load(bottom, top);
		} else {
			output_.load(top, bottom);
		}
	} else {
		if(double_height_offset_) {
			output_.reset();
		} else {
			const auto top_address = line_ >> 1;
			const uint8_t top = font[c - 32][top_address];
			const uint8_t bottom = font[c - 32][std::min(9, top_address + 1)];

			if(odd_frame_) {
				output_.load(bottom, top);
			} else {
				output_.load(top, bottom);
			}
		}
	}
}
